refactor(filtered ast visitor): refactor DocumentSymbolCollector to fit the CRTP design of RecursiveASTVisitor#427
Conversation
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
📝 WalkthroughWalkthroughTraversal logic was simplified across the AST visitors: explicit suppression of implicit declarations and implicit template instantiations was removed, DocumentSymbol collection now uses an explicit Changes
Sequence Diagram(s)sequenceDiagram
participant AST as clang::ASTContext
participant Filtered as FilteredASTVisitor (Derived)
participant Base as Base::TraverseDecl
participant Symbols as DocumentSymbolCollector
participant Semantic as SemanticVisitor
AST->>Filtered: start TraverseAST
Filtered->>Base: TraverseDecl(decl) -- direct delegation (no implicit suppression)
Base->>Filtered: visit child decls / callbacks
alt NamedDecl encountered (DocumentSymbols)
Symbols->>Symbols: push_symbol(NamedDecl)
Symbols->>Base: Base::Traverse*Decl(child)
end
Semantic->>AST: run() always calls Base::TraverseAST(unit.context())
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@scripts/setup-llvm.py`:
- Around line 205-215: The config parsing loop in scripts/setup-llvm.py is too
strict: change the loop that reads cfg.read_text(...) so it normalizes each line
(strip leading/trailing whitespace), accepts both "--sysroot=PATH" and
"--sysroot PATH" forms, strips surrounding quotes from PATH, resolves and
validates the Path, and continue scanning all lines but keep the last valid
sysroot found (instead of returning the first); ensure you still handle OSError
as before and return None if no valid sysroot is found. Use the existing
variables/functions (cfg.read_text, sysroot, Path.resolve(), sysroot.exists())
to locate and update the parsing logic.
- Around line 341-358: The code currently sets needs_install=True when a
provided candidate exists but fails llvm_install_matches_compiler, but leaves
install_path unset so the bundled LLVM is extracted into install_root instead of
the user-specified path; update the branch that checks
llvm_install_matches_compiler(candidate) to explicitly honor the user's
candidate by setting install_path = candidate (and leave needs_install = True so
installation proceeds), and change the log message to clearly state "will
install bundled LLVM to provided path {candidate} (outside active compiler
sysroot)"; reference the symbols args.install_path, candidate,
llvm_install_matches_compiler, install_path, needs_install and install_root to
locate and modify the logic and message.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f72d2ccd-4edb-4fa8-b426-72644940a542
📒 Files selected for processing (3)
cmake/llvm.cmakescripts/setup-llvm.pysrc/semantic/filtered_ast_visitor.h
There was a problem hiding this comment.
Pull request overview
This PR refactors AST traversal to remove redundant implicit-declaration/template-instantiation filtering, and hardens LLVM dependency setup to avoid linking against LLVM installs that are ABI-incompatible with sandboxed toolchains (e.g., pixi/NixOS).
Changes:
- Simplifies
FilteredASTVisitorby dropping explicit filtering of implicit decls and implicit template instantiations. - Enhances
setup-llvm.pyto detect the active compiler sysroot and reject LLVM installs outside it. - Updates LLVM CMake integration to also include
${binary_dir}/includeso downloaded “private” Clang headers are found.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
src/semantic/filtered_ast_visitor.h |
Removes implicit/implicit-instantiation skip logic from decl traversal. |
scripts/setup-llvm.py |
Adds compiler sysroot detection and filters out LLVM installs that fall outside the sysroot. |
cmake/llvm.cmake |
Adds build-tree include directory for fetched private Clang headers. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
0769540 to
4fa9fe6
Compare
RecursiveASTVisitor already skips implicit decls and implicit template instantiations by default via shouldVisitImplicitCode() and shouldVisitTemplateInstantiations(). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
352fcbf to
40bb6ed
Compare
Move DocumentSymbolCollector off the generic decl hook. This keeps document-symbol logic local and lets RecursiveASTVisitor retain its normal filtering and child traversal.
40bb6ed to
0d606b7
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
src/semantic/filtered_ast_visitor.h (1)
36-52: Stale comment and redundant branch after the simplification.Two small cleanups are worth doing here:
- The comment on lines 36–37 ("Clang doesn't visit implicit declarations and implicit template instantiations by default") used to justify the explicit skip logic that has now been removed. In its current position it sits above the
TranslationUnitDeclbranch, which is unrelated — it reads as explaining the TU handling. Either move it back to where the implicit-decl filtering reasoning applies (or drop it).- Once
interested_onlyis false, both branches do exactlyreturn Base::TraverseDecl(decl);, so the outerisa<TranslationUnitDecl>block can collapse into a single early return guarded byinterested_only.♻️ Suggested tightening
- /// Clang doesn't visit implicit declarations and - /// implicit template instantiations by default. - if(llvm::isa<clang::TranslationUnitDecl>(decl)) { - if(interested_only) { - for(auto top: unit.top_level_decls()) { - if(!TraverseDecl(top)) { - return false; - } - } - return true; - } - - return Base::TraverseDecl(decl); - } - - return Base::TraverseDecl(decl); + /// When `interested_only` is set, only walk the top-level decls of the + /// interested file instead of the full translation unit. Implicit decls + /// and implicit template instantiations are skipped by + /// `RecursiveASTVisitor` by default, so no extra filtering is needed. + if(interested_only && llvm::isa<clang::TranslationUnitDecl>(decl)) { + for(auto top: unit.top_level_decls()) { + if(!TraverseDecl(top)) { + return false; + } + } + return true; + } + + return Base::TraverseDecl(decl);🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/semantic/filtered_ast_visitor.h` around lines 36 - 52, The comment about implicit declarations is stale and the TranslationUnitDecl branch has redundant returns; remove or relocate the comment (it belongs with the implicit-decl filtering logic, not above the TranslationUnitDecl handling), then simplify the control flow: if interested_only is true, iterate unit.top_level_decls() and call TraverseDecl(top) (return false on failure and true after the loop), otherwise just call and return Base::TraverseDecl(decl); eliminate the unnecessary isa<clang::TranslationUnitDecl> outer branch so TranslationUnitDecl handling only appears as the interested_only early-path and otherwise falls through to Base::TraverseDecl.src/feature/document_symbols.cpp (1)
115-145:push_symbolrelies on guaranteed copy elision ofGuard— worth a quick defensive tweak.The pattern returns a locally-defined
Guardby value frompush_symboland captures it withauto guard = this->push_symbol(decl);. C++17 mandatory prvalue copy elision makes this sound today, and the user-provided destructor suppresses the implicit move so any accidental future refactor that forces a copy (e.g., binding through a function-like wrapper, storing in a variant) would invoke the implicit copy constructor and run the sourceGuard’s destructor prematurely, restoringresult.cursorbefore traversal of children completes.Consider either marking
Guardas move-only (and defaulting the move constructor while deleting copy), or clearingpreviousin the moved-from guard. Low priority — current code is correct.♻️ Defensive hardening
struct Guard { std::vector<InternalSymbol>* previous; std::vector<InternalSymbol>** cursor; + Guard(const Guard&) = delete; + Guard& operator=(const Guard&) = delete; + Guard(Guard&& other) noexcept + : previous(std::exchange(other.previous, nullptr)), + cursor(other.cursor) {} + Guard& operator=(Guard&&) = delete; + ~Guard() { if(previous) { *cursor = previous; } } };🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/feature/document_symbols.cpp` around lines 115 - 145, push_symbol returns a local Guard with a non-trivial destructor which can be accidentally copied and prematurely restore result.cursor; make Guard move-only to prevent accidental copies by deleting its copy constructor/assignment and defaulting the move ctor/assign (or alternatively ensure moved-from Guard clears previous so its destructor is no-op). Update the inner struct Guard in push_symbol: delete copy ctor/assign and default move ctor/assign (or clear previous on move) and keep the destructor logic to restore result.cursor from previous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/feature/document_symbols.cpp`:
- Around line 148-170: The TRAVERSE_SYMBOL_DECL macro currently omits
typedef/type alias nodes so TypedefNameDecl handling in symbol_detail is dead:
update the macro invocations to also include Typedef, TypeAlias, and
TypeAliasTemplate (i.e., add TRAVERSE_SYMBOL_DECL(Typedef);
TRAVERSE_SYMBOL_DECL(TypeAlias); TRAVERSE_SYMBOL_DECL(TypeAliasTemplate);) so
push_symbol is called for those decls and symbol_detail's TypedefNameDecl branch
is reachable; alternatively, if you prefer removing dead code, delete the
TypedefNameDecl/type alias branch from symbol_detail. After making the change,
add unit/integration tests that assert typedef and using/type-alias-template
declarations appear in the document-symbols output.
---
Nitpick comments:
In `@src/feature/document_symbols.cpp`:
- Around line 115-145: push_symbol returns a local Guard with a non-trivial
destructor which can be accidentally copied and prematurely restore
result.cursor; make Guard move-only to prevent accidental copies by deleting its
copy constructor/assignment and defaulting the move ctor/assign (or
alternatively ensure moved-from Guard clears previous so its destructor is
no-op). Update the inner struct Guard in push_symbol: delete copy ctor/assign
and default move ctor/assign (or clear previous on move) and keep the destructor
logic to restore result.cursor from previous.
In `@src/semantic/filtered_ast_visitor.h`:
- Around line 36-52: The comment about implicit declarations is stale and the
TranslationUnitDecl branch has redundant returns; remove or relocate the comment
(it belongs with the implicit-decl filtering logic, not above the
TranslationUnitDecl handling), then simplify the control flow: if
interested_only is true, iterate unit.top_level_decls() and call
TraverseDecl(top) (return false on failure and true after the loop), otherwise
just call and return Base::TraverseDecl(decl); eliminate the unnecessary
isa<clang::TranslationUnitDecl> outer branch so TranslationUnitDecl handling
only appears as the interested_only early-path and otherwise falls through to
Base::TraverseDecl.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 328f5477-a2d0-4aa3-994a-8927f3e9ea1c
📒 Files selected for processing (3)
src/feature/document_symbols.cppsrc/semantic/filtered_ast_visitor.hsrc/semantic/semantic_visitor.h
| #define TRAVERSE_SYMBOL_DECL(type) \ | ||
| bool Traverse##type##Decl(clang::type##Decl* decl) { \ | ||
| auto guard = this->push_symbol(decl); \ | ||
| return Base::Traverse##type##Decl(decl); \ | ||
| } | ||
|
|
||
| TRAVERSE_SYMBOL_DECL(Namespace); | ||
| TRAVERSE_SYMBOL_DECL(Enum); | ||
| TRAVERSE_SYMBOL_DECL(EnumConstant); | ||
| TRAVERSE_SYMBOL_DECL(Function); | ||
| TRAVERSE_SYMBOL_DECL(CXXMethod); | ||
| TRAVERSE_SYMBOL_DECL(CXXConstructor); | ||
| TRAVERSE_SYMBOL_DECL(CXXDestructor); | ||
| TRAVERSE_SYMBOL_DECL(CXXConversion); | ||
| TRAVERSE_SYMBOL_DECL(CXXDeductionGuide); | ||
| TRAVERSE_SYMBOL_DECL(Record); | ||
| TRAVERSE_SYMBOL_DECL(CXXRecord); | ||
| TRAVERSE_SYMBOL_DECL(Field); | ||
| TRAVERSE_SYMBOL_DECL(Var); | ||
| TRAVERSE_SYMBOL_DECL(Binding); | ||
| TRAVERSE_SYMBOL_DECL(Concept); | ||
|
|
||
| #undef TRAVERSE_SYMBOL_DECL |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Look for any tests asserting presence/absence of typedef/type-alias entries in document symbols
rg -nP --type=cpp -C3 'document_symbols|DocumentSymbol' -g '*test*'
rg -nP --type=cpp -C2 '\b(typedef|using .* =)' -g '*document*symbol*test*'Repository: clice-io/clice
Length of output: 5174
🏁 Script executed:
# Find and read the document_symbols.cpp file to verify the TRAVERSE_SYMBOL_DECL macro
fd -t f 'document_symbols\.cpp' -x cat -n {}Repository: clice-io/clice
Length of output: 9555
🏁 Script executed:
# Check what's in symbol_detail function around lines 87-90
fd -t f 'document_symbols\.cpp' -x sed -n '80,100p' {}Repository: clice-io/clice
Length of output: 723
🏁 Script executed:
# Look at the test file for any typedef/type alias test cases
cat -n tests/unit/feature/document_symbol_tests.cpp | head -200Repository: clice-io/clice
Length of output: 4423
🏁 Script executed:
# Search for TypedefDecl, TypeAliasDecl, TypeAliasTemplateDecl in the entire codebase
rg -n 'TypedefDecl|TypeAliasDecl|TypeAliasTemplateDecl' --type=cppRepository: clice-io/clice
Length of output: 1442
🏁 Script executed:
# Check what other Traverse methods exist in DocumentSymbolCollector or similar visitor classes
rg -A2 'Traverse.*Decl\(' src/feature/document_symbols.cpp
# Search for all type alias and typedef related declaration types in Clang
rg -n 'TypeAlias|TypedefDecl|TemplateDecl' --type=cpp src/ | grep -E '(Typedef|TypeAlias|Template.*Decl)' | head -20Repository: clice-io/clice
Length of output: 2549
🏁 Script executed:
# Check if there are template specialization or other template-related types we might be missing
rg -n 'ClassTemplateDecl|FunctionTemplateDecl|VarTemplateDecl' --type=cpp | head -20Repository: clice-io/clice
Length of output: 2166
🏁 Script executed:
# Verify that Typedef, TypeAlias, TypeAliasTemplate exist as Clang declaration types
# and check if there are any usages in the current codebase that would be missed
rg -n 'TypedefDecl|TypeAliasDecl|TypeAliasTemplateDecl' --type=cpp src/feature/document_symbols.cppRepository: clice-io/clice
Length of output: 40
🏁 Script executed:
# Check if there's a FilteredASTVisitor base class that might have these Traverse methods
fd -t f 'filtered_ast_visitor' -x cat -n {} | head -50Repository: clice-io/clice
Length of output: 1942
🏁 Script executed:
# Check if RecursiveASTVisitor has Traverse methods for Typedef, TypeAlias, TypeAliasTemplate
# by searching Clang documentation or code patterns
rg -n 'TraverseTypedef|TraverseTypeAlias|Typedef.*Decl' --type=cpp src/ | head -10Repository: clice-io/clice
Length of output: 934
🏁 Script executed:
# Look at git history to see if these were removed recently
git log --oneline --all -S 'TRAVERSE_SYMBOL_DECL(Typedef' -- src/feature/document_symbols.cpp 2>/dev/null | head -5Repository: clice-io/clice
Length of output: 40
🏁 Script executed:
# Check if there's any pattern that shows how template declarations should be handled
rg -B5 -A5 'ClassTemplateDecl|FunctionTemplateDecl' src/feature/document_symbols.cppRepository: clice-io/clice
Length of output: 40
🏁 Script executed:
# Check symbol_kind.cpp to see how it recognizes TypedefNameDecl
sed -n '1,30p' src/semantic/symbol_kind.cppRepository: clice-io/clice
Length of output: 1457
🏁 Script executed:
# Check if code_completion handles TypedefDecl in TRAVERSE or similar
rg -B5 -A5 'TypedefNameDecl' src/feature/code_completion.cpp | head -30Repository: clice-io/clice
Length of output: 515
Typedefs and type aliases are excluded from document symbols, leaving dead code in symbol_detail.
The TRAVERSE_SYMBOL_DECL macro (lines 154–168) explicitly lists only these declaration types, but excludes Typedef, TypeAlias, and TypeAliasTemplate. Meanwhile, symbol_detail (lines 87–88) contains a branch that prints "type alias" for TypedefNameDecl—code that will never execute because these nodes don't reach push_symbol.
Since symbol_kind.cpp already recognizes TypedefNameDecl as SymbolKind::Type, and other features like code_completion.cpp handle both TypedefNameDecl and TypeAliasTemplateDecl, users will incorrectly stop seeing typedef/using declarations in the document-symbols outline (and potentially template type aliases if they are also missing).
Either remove the stale TypedefNameDecl handling from symbol_detail, or extend the macro to include these types:
Suggested fix
TRAVERSE_SYMBOL_DECL(Binding);
TRAVERSE_SYMBOL_DECL(Concept);
+ TRAVERSE_SYMBOL_DECL(Typedef);
+ TRAVERSE_SYMBOL_DECL(TypeAlias);
+ TRAVERSE_SYMBOL_DECL(TypeAliasTemplate);Also add test coverage for typedef and type alias declarations to prevent regressions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/feature/document_symbols.cpp` around lines 148 - 170, The
TRAVERSE_SYMBOL_DECL macro currently omits typedef/type alias nodes so
TypedefNameDecl handling in symbol_detail is dead: update the macro invocations
to also include Typedef, TypeAlias, and TypeAliasTemplate (i.e., add
TRAVERSE_SYMBOL_DECL(Typedef); TRAVERSE_SYMBOL_DECL(TypeAlias);
TRAVERSE_SYMBOL_DECL(TypeAliasTemplate);) so push_symbol is called for those
decls and symbol_detail's TypedefNameDecl branch is reachable; alternatively, if
you prefer removing dead code, delete the TypedefNameDecl/type alias branch from
symbol_detail. After making the change, add unit/integration tests that assert
typedef and using/type-alias-template declarations appear in the
document-symbols output.
Uh oh!
There was an error while loading. Please reload this page.